/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2009 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

#include "SDL_timer.h"
#include "SDL_audio.h"
#include "../SDL_audiomem.h"
#include "../SDL_audio_c.h"
#include "../SDL_audiodev_c.h"
#include "SDL_cellaudio.h"

#include <cell/mstream.h>

/* Data */
static struct SDL_PrivateAudioData Data;

/* Conversion functions */
void					CELL_ConvertU8				(int16_t* aDest, void* aSource, uint32_t aCount);
void					CELL_ConvertS8				(int16_t* aDest, void* aSource, uint32_t aCount);
void					CELL_ConvertU16MSB			(int16_t* aDest, void* aSource, uint32_t aCount);
void					CELL_ConvertU16LSB			(int16_t* aDest, void* aSource, uint32_t aCount);

/* Audio driver functions */
static int				CELLAUD_OpenAudio			(_THIS, SDL_AudioSpec *spec);
static void				CELLAUD_WaitAudio			(_THIS);
static void				CELLAUD_PlayAudio			(_THIS);
static Uint8*			CELLAUD_GetAudioBuf			(_THIS);
static void				CELLAUD_CloseAudio			(_THIS);
static void				CELLAUD_DeleteDevice		(_THIS);
static int				CELLAUD_Available			(void);


/* Process thread */
static void				MultiStreamCallback			(int streamNumber, void * userData, int cType, void * pWriteBuffer, int nBufferSize)
{
	if((cType == CELL_MS_CALLBACK_MOREDATA) && (!SDL_SemValue(Data.Thread.Semaphore)))
	{
		SDL_LockMutex(Data.Thread.Mutex);
		memcpy(pWriteBuffer, Data.Buffers.Mix, nBufferSize);
		SDL_SemPost(Data.Thread.Semaphore);
		SDL_UnlockMutex(Data.Thread.Mutex);
	}
	else if(cType == CELL_MS_CALLBACK_FINISHSTREAM)
	{
		Data.MultiStream.DeadFlag = 1;
	}
}

static int				ProcessAudioThread			(_THIS)
{
	cellAudioPortStart(Data.Audio.Port);

	while(!Data.Thread.ExitFlag)
	{
		sys_timer_usleep(1000000 / 60 / 2);

		if(!Data.MultiStream.DeadFlag)
		{
			cellMSSystemSignalSPU();
			cellMSSystemGenerateCallbacks();
		}
	}

	cellAudioPortStop(Data.Audio.Port);

	return 0;
}

/* Audio driver bootstrap functions */
static int				CELLAUD_Available			(void) {return 1;}

static void				CELLAUD_DeleteDevice		(_THIS)
{
	/* Stop thread */
	Data.Thread.ExitFlag = 1;

	SDL_WaitThread(Data.Thread.Thread, 0);
	SDL_DestroySemaphore(Data.Thread.Semaphore);
	SDL_DestroyMutex(Data.Thread.Mutex);

	/* Stop Multistream */
	cellMSSystemClose();
	free(Data.MultiStream.Memory);
	free(Data.MultiStreamMP3.Memory);

	/* Stop libaudio */
	cellAudioPortClose(Data.Audio.Port);
	cellAudioQuit();

	/* Free buffers */
	free(Data.Buffers.Mix);
	free(Data.Buffers.Convert);

	/* Free device */
	SDL_free(this);
}

static SDL_AudioDevice*	CELLAUD_CreateDevice		(int devindex)
{
	SDL_AudioDevice *this;

	/* Initialize all variables that we clean on shutdown */
	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
	if(this)
	{
		SDL_memset(this, 0, (sizeof *this));
		this->hidden = &Data;
	}

	SDL_memset(&Data, 0, (sizeof Data));

	/* Set the function pointers */
	this->OpenAudio = CELLAUD_OpenAudio;
	this->WaitAudio = CELLAUD_WaitAudio;
	this->PlayAudio = CELLAUD_PlayAudio;
	this->GetAudioBuf = CELLAUD_GetAudioBuf;
	this->CloseAudio = CELLAUD_CloseAudio;

	this->free = CELLAUD_DeleteDevice;

	/* Allocate buffers */
	Data.Buffers.Mix = memalign(128, 8192 * sizeof(uint32_t));
	Data.Buffers.Convert = memalign(128, 8192 * sizeof(uint32_t));

	/* Init audio */
	CellAudioPortParam portparam = {CELL_AUDIO_PORT_8CH, CELL_AUDIO_BLOCK_16, CELL_AUDIO_PORTATTR_BGM, 1};
	cellAudioInit();
	cellAudioPortOpen(&portparam, &Data.Audio.Port);
	cellAudioGetPortConfig(Data.Audio.Port, &Data.Audio.Config);

	/* Setup multistream */
	sys_spu_initialize(6, 2);
	cellMSSystemConfigureSysUtil();

	CellMSSystemConfig MultiStreamConfig = {4, 1, 0, CELL_MS_NOFLAGS};
	Data.MultiStream.Memory = memalign(128, cellMSSystemGetNeededMemorySize(&MultiStreamConfig));
	cellMSSystemInitSPUThread(Data.MultiStream.Memory, &MultiStreamConfig, 100);
	Data.MultiStream.Channel = -1;

	cellMSSystemConfigureLibAudio(&portparam, &Data.Audio.Config);

	/* Setup MP3 */
	Data.MultiStreamMP3.Memory = memalign(128, cellMSMP3GetNeededMemorySize(8));
	cellMSMP3Init(8, Data.MultiStreamMP3.Memory);

	/* Setup volume */
	float volumes[64];
	for(int i = 0; i != 64; i ++)
	{
		volumes[i] = ((i % 8) == (i / 8)) ? 1.0f : 0.0f;
	}

	cellMSCoreSetVolume64(CELL_MS_BUS_FLAG | 1, CELL_MS_WET, volumes);
	cellMSCoreSetVolume64(CELL_MS_MASTER_BUS, CELL_MS_DRY, volumes);

	/* Create thread */
	Data.Thread.Semaphore = SDL_CreateSemaphore(1);
	Data.Thread.Mutex = SDL_CreateMutex();
	Data.Thread.Thread = SDL_CreateThread((int(*)(void*))ProcessAudioThread, this);

	return this;
}

/* This function waits until it is possible to write a full sound buffer */
static void				CELLAUD_WaitAudio			(_THIS)
{
}

static void				CELLAUD_PlayAudio			(_THIS)
{
	if(Data.Buffers.ConvertFunction)
	{
		SDL_SemWait(Data.Thread.Semaphore);
		SDL_LockMutex(Data.Thread.Mutex);
		Data.Buffers.ConvertFunction((int16_t*)Data.Buffers.Mix, Data.Buffers.Convert, Data.Buffers.Length);
	}

	SDL_UnlockMutex(Data.Thread.Mutex);
}

static Uint8*			CELLAUD_GetAudioBuf			(_THIS)
{
	if(Data.Buffers.ConvertFunction)
	{
		return (Uint8*)Data.Buffers.Convert;
	}
	else
	{
		SDL_SemWait(Data.Thread.Semaphore);
		SDL_LockMutex(Data.Thread.Mutex);
		return (Uint8*)Data.Buffers.Mix;
	}
}

static void				CELLAUD_CloseAudio			(_THIS)
{
	/* Stop stream */
	cellMSStreamSetSecondRead(Data.MultiStream.Channel, 0, 0);
	while(!Data.MultiStream.DeadFlag); //?
	cellMSStreamClose(Data.MultiStream.Channel);
	while(!(cellMSStreamGetStatus(Data.MultiStream.Channel) & CELL_MS_STREAM_CLOSED));

	free(Data.MultiStream.Buffers[0]);
	free(Data.MultiStream.Buffers[1]);

	/* Reset buffers */
	if(SDL_SemValue(Data.Thread.Semaphore) != 1)
	{
		SDL_SemPost(Data.Thread.Semaphore);
	}
}

static int				CELLAUD_OpenAudio			(_THIS, SDL_AudioSpec *spec)
{
	spec->samples = spec->samples >= 4096 ? 4096 : spec->samples;
	spec->samples = spec->samples < 256 ? 256 : spec->samples;
	Data.Buffers.Length = spec->samples * spec->channels;
	SDL_CalculateAudioSpec(spec);

	/* Find any converter function */
	Data.Buffers.ConvertFunction = 0;
	     if(spec->format == AUDIO_U8)		Data.Buffers.ConvertFunction = CELL_ConvertU8;
	else if(spec->format == AUDIO_S8)		Data.Buffers.ConvertFunction = CELL_ConvertS8;
	else if(spec->format == AUDIO_U16MSB)	Data.Buffers.ConvertFunction = CELL_ConvertU16MSB;
	else if(spec->format == AUDIO_U16LSB)	Data.Buffers.ConvertFunction = CELL_ConvertU16LSB;

	/* Setup audio stream */
	for(int i = 0; i != 2; i ++)
	{
		Data.MultiStream.Buffers[i] = memalign(128, Data.Buffers.Length * 2);
		memset(Data.MultiStream.Buffers[i], 0, Data.Buffers.Length * 2);
	}

	CellMSInfo StreamInfo = {CELL_MS_MASTER_BUS, Data.MultiStream.Buffers[0], Data.Buffers.Length * 2, Data.MultiStream.Buffers[1], Data.Buffers.Length * 2, spec->freq, spec->channels, 0,
							(spec->format == AUDIO_S16LSB) ? CELL_MS_16BIT_LITTLE : CELL_MS_16BIT_BIG, CELL_MS_STREAM_NOFLAGS};

    Data.MultiStream.Channel = cellMSStreamOpen();

    cellMSStreamSetInfo(Data.MultiStream.Channel, &StreamInfo);
    cellMSStreamSetCallbackFunc(Data.MultiStream.Channel, MultiStreamCallback);
	cellMSCoreSetVolume1(Data.MultiStream.Channel, CELL_MS_DRY, CELL_MS_SPEAKER_FL, CELL_MS_CHANNEL_0, 1.0f);
	cellMSCoreSetVolume1(Data.MultiStream.Channel, CELL_MS_DRY, CELL_MS_SPEAKER_FR, (spec->channels > 1) ? CELL_MS_CHANNEL_1 : CELL_MS_CHANNEL_0, 1.0f);
	cellMSStreamPlay(Data.MultiStream.Channel);

	Data.MultiStream.DeadFlag = 0;
	return(0);
}


AudioBootStrap CELLAUD_bootstrap = {"cell", "PS3 SDK Audio", CELLAUD_Available, CELLAUD_CreateDevice};

